package com.sxf.mybatis.enhance;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.Assert;

/**
 * 习惯用File来进行资源的IO操作。 但实际上Java对资源的访问定位基本上是采用URL方式来做的。
 * 
 * File方式的好处：File对象可以很方便的进行读写操作
 * URL方式的好处：可以以描述zip/jar包中的文件，以及位于网络上的资源文件。虽然这些文件只能读不能写
 * ，但URL对象却是是唯一能完整描述各种资源定位的唯一手段。
 * 
 * 这个设计思路上的差异随着JEF的变迁日益突出。JEF最初试图封装出一种可读写的资源统一API，（以File为主）。
 * 但是随后又碰到各种复杂的资源定位路径的问题，最后越搞越复杂。
 * 
 * 这个类就是当初试图让URL类的资源向File类靠拢而设计的。不管怎么说，这个类至少起到了一个本地缓存的作用。
 * 或许是今后对统一资源定位抽象设计进行改善的一个方法。
 * 
 * @author jiyi
 *
 */
public class URLFile extends File {
	private static final long serialVersionUID = 6607216819937577215L;
	private File rwFile;

	private File zipFile;// 所在Zip包的文件
	private String zippedPath;// 在Zip包中的路径

	private URL url;
	private String type;
	private String path;
	// 仅当URL时
	private boolean exist = true;
	private File data;

	/**
	 * 将非本地文件复制为一个临时文件
	 */
	private void loadData() {
		if (exist && data != null)
			return;
		try {
			InputStream in = url.openStream();
			data = IOUtils.saveAsTempFile(in);
		} catch (IOException e) {
			LogUtil.exception("loading " + url.toString() + " error!", e);
			exist = false;
		} catch (Throwable t) {
			LogUtil.exception("Load file " + url + " error!", t);
			exist = false;
		}
	}

	/**
	 * 是否一个本地文件
	 * 
	 * @return
	 */
	public boolean isLocalFile() {
		return rwFile != null;
	}

	/**
	 * 返回本地文件，只有当{@link #isLocalFile()}=true时才能使用
	 * 
	 * @return
	 */
	public File getLocalFile() {
		Assert.notNull(rwFile);
		return rwFile;
	}

	public FileInputStream getInputStream() throws IOException {
		if (rwFile != null) {
			return new FileInputStream(rwFile);
		}
		loadData();
		if (data != null) {
			return new FileInputStream(data);
		}
		throw new FileNotFoundException("File " + path + " not exist!");
	}

	public URLFile(URI uri) {
		super("");
		try {
			this.url = uri.toURL();
		} catch (MalformedURLException e) {
			throw new IllegalArgumentException(e);
		}
		init();
	}

	public URLFile(URL url) {
		super("");
		Assert.notNull(url);
		this.url = url;
		init();
	}

	private void init() {
		this.type = url.getProtocol().toLowerCase();
		try {
			path = URLDecoder.decode(url.getPath(), "UTF-8");
		} catch (UnsupportedEncodingException e) {
			// Never Happens
		}

		if ("file".equals(type)) {
			rwFile = new File(path);
		} else if ("jar".equals(type)) {// 位于JAR文件中
			String zipName = StringUtils.substringAfter(
					StringUtils.substringBefore(path, "!"), "file:");
			while (zipName.startsWith("//")) {
				zipName = zipName.substring(1);// 丢弃一个/
			}
			this.zipFile = new File(zipName);
			this.zippedPath = StringUtils.substringAfter(path, "!");
		} else if ("zip".equals(type)) {
			String zipName = StringUtils.substringBefore(path, "!");
			this.zipFile = new File(zipName);
			this.zippedPath = StringUtils.substringAfter(path, "!");
		}
		if (path.charAt(0) == '/')
			path = path.substring(1);
	}

	@Override
	public String getName() {
		return super.getName();
	}

	@Override
	public String getParent() {
		if (rwFile != null) {
			return rwFile.getParent();
		}
		String s = url.toString();
		if (s.indexOf('/') > -1) {
			return StringUtils.substringBeforeLast(url.toString(), "/");
		} else {
			return null;
		}

	}

	@Override
	public File getParentFile() {
		if (rwFile != null) {
			return rwFile.getParentFile();
		}
		String parent = getParent();
		if (parent == null)
			return null;
		parent = parent.replace('\\', '/');
		parent = StringUtils.substringBeforeLast(parent, "/");
		if (parent.endsWith("!")) {
			parent = parent.substring(4, parent.length() - 1);
		}
		try {
			URL url = new URL(parent);
			return new URLFile(url);
		} catch (MalformedURLException e) {
			throw new IllegalArgumentException("error create Parent UrlFile:["
					+ parent + "]" + e.getMessage());
		}
	}

	@Override
	public String getPath() {
		if (rwFile != null) {
			return rwFile.getPath();
		}
		return path;
	}

	@Override
	public boolean isAbsolute() {
		if (rwFile != null) {
			return rwFile.isAbsolute();
		}
		return true;
	}

	@Override
	public String getAbsolutePath() {
		if (rwFile != null) {
			return rwFile.getAbsolutePath();
		}
		return path;
	}

	@Override
	public File getAbsoluteFile() {
		if (rwFile != null) {
			return rwFile.getAbsoluteFile();
		}
		return this;
	}

	@Override
	public String getCanonicalPath() throws IOException {
		if (rwFile != null) {
			return rwFile.getCanonicalPath();
		}
		return path;
	}

	@Override
	public File getCanonicalFile() throws IOException {
		if (rwFile != null) {
			return rwFile.getCanonicalFile();
		}
		return this;
	}

	@Override
	public URL toURL() throws MalformedURLException {
		return url;
	}

	@Override
	public URI toURI() {
		if (rwFile != null) {
			return rwFile.toURI();
		}
		try {
			return url.toURI();
		} catch (URISyntaxException e) {
			throw new IllegalArgumentException(e);
		}
	}

	@Override
	public boolean canRead() {
		if (rwFile != null) {
			return rwFile.canRead();
		}
		loadData();
		return exist;
	}

	@Override
	public boolean canWrite() {
		if (rwFile != null) {
			return rwFile.canWrite();
		}
		return false;
	}

	@Override
	public boolean exists() {
		if (rwFile != null) {
			return rwFile.exists();
		}
		loadData();
		return exist;
	}

	@Override
	public boolean isDirectory() {
		if (rwFile != null) {
			return rwFile.isDirectory();
		}
		return path.endsWith("/");
	}

	@Override
	public boolean isFile() {
		if (rwFile != null) {
			return rwFile.isFile();
		}
		return !path.endsWith("/");
	}

	@Override
	public boolean isHidden() {
		if (rwFile != null) {
			return rwFile.isHidden();
		}
		return false;
	}

	@Override
	public long lastModified() {
		if (rwFile != null) {
			return rwFile.lastModified();
		} else if (zipFile != null) {
			return zipFile.lastModified();// 返回ZIP文件内的日期
		}
		loadData();
		if (exist) {
			return data.lastModified();
		}
		return -1;
	}

	public boolean isZipFile() {
		return this.zipFile != null;
	}

	public File getZipContainer() {
		Assert.notNull(zipFile);
		return this.zipFile;
	}

	public String getZipEntryPath() {
		Assert.notNull(zippedPath);
		return this.zippedPath;
	}

	@Override
	public long length() {
		if (rwFile != null) {
			return rwFile.length();
		}
		loadData();
		if (exist) {
			return data.length();
		}
		return -1;
	}

	@Override
	public boolean createNewFile() throws IOException {
		if (rwFile != null) {
			return rwFile.createNewFile();
		}
		return false;
	}

	@Override
	public boolean delete() {
		if (rwFile != null) {
			return rwFile.delete();
		}
		if (data != null) {
			data.delete();
			exist = false;
			data = null;
		}
		return false;
	}

	@Override
	public void deleteOnExit() {
		if (rwFile != null) {
			rwFile.deleteOnExit();
		}
		if (data != null) {
			data.deleteOnExit();
			exist = false;
			data = null;
		}
	}

	@Override
	public String[] list() {
		if (rwFile != null) {
			return rwFile.list();
		}
		return ArrayUtils.EMPTY_STRING_ARRAY;
	}

	@Override
	public String[] list(FilenameFilter filter) {
		if (rwFile != null) {
			return rwFile.list(filter);
		}
		return ArrayUtils.EMPTY_STRING_ARRAY;
	}

	@Override
	public File[] listFiles() {
		if (rwFile != null) {
			return rwFile.listFiles();
		}
		return new File[0];
	}

	@Override
	public File[] listFiles(FilenameFilter filter) {
		if (rwFile != null) {
			return rwFile.listFiles(filter);
		}
		return new File[0];
	}

	@Override
	public File[] listFiles(FileFilter filter) {
		if (rwFile != null) {
			return rwFile.listFiles(filter);
		}
		return new File[0];
	}

	@Override
	public boolean mkdir() {
		if (rwFile != null) {
			return rwFile.mkdir();
		}
		return false;
	}

	@Override
	public boolean mkdirs() {
		if (rwFile != null) {
			return rwFile.mkdirs();
		}
		return false;
	}

	@Override
	public boolean renameTo(File dest) {
		if (rwFile != null) {
			return rwFile.renameTo(dest);
		}
		return false;
	}

	@Override
	public boolean setLastModified(long time) {
		if (rwFile != null) {
			return rwFile.setLastModified(time);
		}
		if (data != null) {
			return data.setLastModified(time);
		}
		return false;
	}

	@Override
	public boolean setReadOnly() {
		if (rwFile != null) {
			return rwFile.setReadOnly();
		}
		return true;
	}

	@Override
	public int compareTo(File pathname) {
		if (rwFile != null) {
			return rwFile.compareTo(pathname);
		}
		if (pathname == null)
			return 1;
		return getPath().compareTo(pathname.getPath());
	}

	@Override
	public boolean equals(Object obj) {
		if (obj == null)
			return false;
		if (rwFile != null) {
			return rwFile.equals(obj);
		}
		if (obj instanceof File) {
			return ((File) obj).getPath().equals(this.url.getFile());
		}
		return super.equals(obj);
	}

	@Override
	public int hashCode() {
		if (rwFile != null) {
			return rwFile.hashCode();
		}
		return url.toString().hashCode();
	}

	@Override
	public String toString() {
		if (rwFile != null) {
			return rwFile.toString();
		}
		return url.toString();
	}
}
